﻿// (c) Norbert Huffschmid
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ServiceModel;
using ShoutBox.Core;
using NProtocol;
using NProtocol.Codec;
using log4net;

namespace ShoutBox.Service
{
    /// <summary>
    /// Shoutbox endpoint controller
    /// </summary>
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
    public class ShoutBoxEndPointController : IShoutBoxEndPointController
    {
        private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);


        /// <summary>
        /// Gets the current shoutbox list
        /// </summary>
        public IList<ISessionMember> SessionMembers
        {
            get { return this.shoutBoxEndPoint.SessionMembers; }
        }


        private ShoutBoxEndPoint shoutBoxEndPoint;
        private IDictionary<string, IShoutBoxEndPointControllerCallback> clients;


        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="shoutBoxEndPoint">Shoutbox endpoint to control</param>
        public ShoutBoxEndPointController(ShoutBoxEndPoint shoutBoxEndPoint)
        {
            this.shoutBoxEndPoint = shoutBoxEndPoint;
            this.shoutBoxEndPoint.SessionMembers.CollectionChanged +=
                new NotifyCollectionChangedEventHandler(ShoutBoxes_CollectionChanged);

            this.clients = new Dictionary<string, IShoutBoxEndPointControllerCallback>();
        }


        /// <summary>
        /// Subscribe for notifications
        /// </summary>
        public void Subscribe()
        {
            IShoutBoxEndPointControllerCallback client = OperationContext.Current.GetCallbackChannel<IShoutBoxEndPointControllerCallback>();
            string sessionId = OperationContext.Current.Channel.SessionId;

            lock (this.clients)
            {
                if (!clients.ContainsKey(sessionId))
                {
                    clients.Add(sessionId, client);

                    OperationContext.Current.Channel.Closing += new EventHandler(Channel_Closing);
                    OperationContext.Current.Channel.Faulted += new EventHandler(Channel_Faulted);

                    log.Info("Client \"" + sessionId + "\" successfully subscribed.");
                }
            }
        }

        /// <summary>
        /// Unsubscribe from notifications
        /// </summary>
        public void Unsubscribe()
        {
            DisconnectClient(OperationContext.Current.Channel.SessionId);
        }

        /// <summary>
        /// Start shouting to target
        /// </summary>
        /// <param name="target">Target</param>
        public void StartShout(ISessionMember target)
        {
            ISessionMember endPointTarget = FindSessionMember(target);
            this.shoutBoxEndPoint.StartShout(endPointTarget);
        }

        /// <summary>
        /// Terminate shouting
        /// </summary>
        /// <param name="target">Target</param>
        public void EndOfShout(ISessionMember target)
        {
            ISessionMember endPointTarget = FindSessionMember(target);
            this.shoutBoxEndPoint.EndOfShout(endPointTarget);
        }

        /// <summary>
        /// Stream shout audio data to remote target
        /// </summary>
        /// <param name="target">Target</param>
        /// <param name="buffer">Audio buffer</param>
        /// <param name="format">Data format</param>
        public void StreamShout(ISessionMember target, byte[] buffer, RealTimeDataFormat format)
        {
            ISessionMember endPointTarget = FindSessionMember(target);
            this.shoutBoxEndPoint.StreamShout(endPointTarget, buffer, format);
        }

        /// <summary>
        /// Shut down all clients
        /// </summary>
        public void ShutDown()
        {
            lock (this.clients)
            {
                // iterate through all connected clients and tell them to shut down
                List<string> clientKeyList = new List<string>(clients.Keys);
                foreach (string sessionId in clientKeyList)
                {
                    IShoutBoxEndPointControllerCallback client = this.clients[sessionId];
                    client.OnShutDown();
                    DisconnectClient(sessionId);
                }
            }
        }


        private void ShoutBoxes_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == NotifyCollectionChangedAction.Add)
            {
                foreach (ISessionMember sessionMember in e.NewItems)
                {
                    foreach (IShoutBoxEndPointControllerCallback client in this.clients.Values)
                    {
                        client.OnSessionMembersAdded(sessionMember);
                    }

                    sessionMember.IsSenderChanged += new EventHandler(sessionMember_IsSenderChanged);
                }
            }
            else if (e.Action == NotifyCollectionChangedAction.Remove)
            {
                foreach (ISessionMember sessionMember in e.OldItems)
                {
                    foreach (IShoutBoxEndPointControllerCallback client in this.clients.Values)
                    {
                        client.OnSessionMembersRemoved(sessionMember);
                    }
                }
            }
            else if (e.Action == NotifyCollectionChangedAction.Reset)
            {
                foreach (IShoutBoxEndPointControllerCallback client in this.clients.Values)
                {
                    client.OnSessionMembersReset();
                }
            }
        }

        private void sessionMember_IsSenderChanged(object sender, EventArgs e)
        {
            ISessionMember sessionMember = sender as ISessionMember;
            if (sessionMember != null)
            {
                foreach (IShoutBoxEndPointControllerCallback client in this.clients.Values)
                {
                    client.OnSessionMemberPropertyChanged("IsSender", sessionMember);
                }
            }
        }

        private void Channel_Closing(object sender, EventArgs e)
        {
            var channel = sender as IContextChannel;
            if (channel != null)
                DisconnectClient(channel.SessionId);
        }

        private void Channel_Faulted(object sender, EventArgs e)
        {
            var channel = sender as IContextChannel;
            if (channel != null)
                DisconnectClient(channel.SessionId);
        }

        private void DisconnectClient(string sessionId)
        {
            lock (this.clients)
            {
                if (clients.ContainsKey(sessionId))
                {
                    clients.Remove(sessionId);

                    log.Info("Client with session ID " + sessionId + " disconnected.");
                }
            }
        }

        private ISessionMember FindSessionMember(ISessionMember wcfSessionMember)
        {
            // compare local instances with instance delivered via WCF
            foreach (ISessionMember sessionMember in this.shoutBoxEndPoint.SessionMembers)
            {
                if (sessionMember.Id == wcfSessionMember.Id)
                    return sessionMember;
            }

            return null;
        }
    }
}
